require 'rmodbus'
require 'serialport'
require 'byebug'

class KioskModbus
  DEFAULT_SERIALPORT = "/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0"
  DEFAULT_BAUD = 9600
  DEFAULT_MODULE_CODE = 1

  COILS_START = 0
  COILS_LEN = 8
  COILS_ADDRS = COILS_LEN.times.map{|x| x}

  INPUTS_START = 0
  INPUTS_LEN = 8
  INPUTS_ADDRS = INPUTS_LEN.times.map{|x| x}

  attr_accessor :serialport, :baud, :module_code, :serialport_client
  attr_accessor :cur_coils, :cur_inputs

  def initialize(serialport = DEFAULT_SERIALPORT, baud = DEFAULT_BAUD, module_code = DEFAULT_MODULE_CODE)
    @serialport = serialport
    @baud = baud
    @module_code = module_code
    @cur_coils = nil
    @cur_inputs = nil

    open_serialport
  end

  def open_serialport
    check_port

    @serialport_client = ModBus::RTUClient.new(serialport, baud)
  end

  def close
    @serialport_client.close
  end

  def check_port
    unless File.exist?(serialport)
      fail "unknown serialport[#{serialport}]"
    end
  end

  def open_coil(addr)
    write_coil(addr, true)
  end

  def close_coil(addr)
    write_coil(addr, false)
  end

  def write_all_coils(coils)
    serialport_client.with_slave(module_code) do |slave|
      slave.write_multiple_coils COILS_START, coils
    end
  end

  def read_all_coils
    serialport_client.with_slave(module_code) do |slave|
      @cur_coils = slave.read_coils COILS_START, COILS_LEN
    end
    @cur_coils
  end

  def read_all_inputs
    serialport_client.with_slave(module_code) do |slave|
      @cur_inputs = slave.read_discrete_inputs INPUTS_START, INPUTS_LEN
    end
    @cur_inputs
  end

  def open_coils_params(*addrs)
    generate_coils_params addrs
  end

  def close_coils_params(*addrs)
    generate_coils_params addrs, false
  end

  private

  def generate_coils_params(addrs, isopen = true)
    coil_value = isopen ? 1 : 1
    result = read_coils

    addrs.each do |addr|
      check_coil_addr(addr)

      result[addr] = coil_value
    end
    result
  end

  def check_coil_addr(addr)
    unless COILS_ADDRS.include? addr
      fail "Coil[#{addr}] out of scope #{COILS_ADDRS}."
    end
  end

  def write_coil(addr, isopen)
    check_coil_addr(addr)
    coil = isopen ? 1 : 0

    serialport_client.with_slave(module_code) do |slave|
      slave.write_single_coil addr, coil
    end
  end
end
